#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/resource.h>
#include <locale.h>
#include <sbase.h>
#include <arpa/inet.h>
#include "stime.h"
#include "base64.h"
#include "base64chunkdhtml.h"
#include "http.h"
#include "iniparser.h"
#include "logger.h"
#include "timer.h"
#include "trie.h"
#include "dbase.h"
#include "xdbase.h"
#ifndef HTTP_BUF_SIZE
#define HTTP_BUF_SIZE           131072
#endif
#ifndef HTTP_LINE_SIZE
#define HTTP_LINE_SIZE          4096
#endif
#ifndef HTTP_QUERY_MAX
#define HTTP_QUERY_MAX          1024
#endif
#define HTTP_LINE_MAX           65536
#define HTTP_RESP_OK            "HTTP/1.0 200 OK\r\n\r\n"
#define HTTP_BAD_REQUEST        "HTTP/1.0 400 Bad Request\r\n\r\n"
#define HTTP_NOT_FOUND          "HTTP/1.0 404 Not Found\r\n\r\n" 
#define HTTP_NOT_MODIFIED       "HTTP/1.0 304 Not Modified\r\n\r\n"
#define HTTP_NO_CONTENT         "HTTP/1.0 206 No Content\r\n\r\n"
#ifndef LL
#define LL(x) ((long long int)x)
#endif
static char *http_default_charset = "UTF-8";
static char *httpd_home = NULL;
static int is_inside_html = 1;
static unsigned char *httpd_index_html_code = NULL;
static int  nhttpd_index_html_code = 0;
static SBASE *sbase = NULL;
static SERVICE *httpd = NULL;
static SERVICE *traced = NULL;
static dictionary *dict = NULL;
static void *http_headers_map = NULL;
static void *logger = NULL;
static XDBASE *xdb = NULL;
static SERVICES *dbs[XDB_DISK_MAX];
static int ndbs = 0;
int httpd_request_handler(HTTP_REQ *httpRQ, IQUERY *query);
/* httpd packet reader */
int httpd_packet_reader(CONN *conn, CB_DATA *buffer)
{
    if(conn)
    {
        return 0;
    }
    return -1;
}

/* converts hex char (0-9, A-Z, a-z) to decimal */
char hex2int(unsigned char hex)
{
    hex = hex - '0';
    if (hex > 9) {
        hex = (hex + '0' - 1) | 0x20;
        hex = hex - 'a' + 11;
    }
    if (hex > 15)
        hex = 0xFF;

    return hex;
}

#define URLDECODE(p, end, ps, high, low)                                                \
do                                                                                      \
{                                                                                       \
    while(*p != '\0' && *p != '&')                                                      \
    {                                                                                   \
        if(ps >= end) break;                                                            \
        else if(*p == '+') {*ps++ = 0x20;++p;continue;}                                 \
        else if(*p == '%')                                                              \
        {                                                                               \
            high = hex2int(*(p + 1));                                                   \
            if (high != 0xFF)                                                           \
            {                                                                           \
                low = hex2int(*(p + 2));                                                \
                if (low != 0xFF)                                                        \
                {                                                                       \
                    high = (high << 4) | low;                                           \
                    if (high < 32 || high == 127) high = '_';                           \
                    *ps++ = high;                                                       \
                    p += 3;                                                             \
                    continue;                                                           \
                }                                                                       \
            }                                                                           \
        }                                                                               \
        *ps++ = *p++;                                                                   \
    }                                                                                   \
    *ps = '\0';                                                                         \
}while(0)

/* packet handler */
int httpd_packet_handler(CONN *conn, CB_DATA *packet)
{
    char buf[HTTP_BUF_SIZE], file[HTTP_PATH_MAX], *p = NULL, *end = NULL;
    HTTP_REQ http_req = {0};
    int ret = -1, n = 0;
    struct stat st = {0};
    IQUERY query = {0};
    
    if(conn)
    {
        //TIMER_INIT(timer);
        p = packet->data;
        end = packet->data + packet->ndata;
        if(http_request_parse(p, end, &http_req, http_headers_map) == -1) goto err_end;
        if(http_req.reqid == HTTP_GET)
        {
            if(http_req.nargvs > 0 && httpd_request_handler(&http_req, &query) > 0) 
            {
                return httpd_query_handler(conn, &query);
            }
            else
            {
                if(is_inside_html && httpd_index_html_code && nhttpd_index_html_code > 0)
                {
                    p = buf;
                    p += sprintf(p, "HTTP/1.0 200 OK\r\nContent-Length: %d\r\n"
                            "Content-Type: text/html;charset=%s\r\n",
                            nhttpd_index_html_code, http_default_charset);
                    if((n = http_req.headers[HEAD_GEN_CONNECTION]) > 0)
                        p += sprintf(p, "Connection: %s\r\n", (http_req.hlines + n));
                    p += sprintf(p, "Connection:Keep-Alive\r\n");
                    p += sprintf(p, "\r\n");
                    conn->push_chunk(conn, buf, (p - buf));
                    return conn->push_chunk(conn, httpd_index_html_code, nhttpd_index_html_code);
                }
                else if(httpd_home)
                {
                    p = file;
                    if(http_req.path[0] != '/')
                        p += sprintf(p, "%s/%s", httpd_home, http_req.path);
                    else
                        p += sprintf(p, "%s%s", httpd_home, http_req.path);
                    if(http_req.path[1] == '\0')
                        p += sprintf(p, "%s", "index.html");
                    if((n = (p - file)) > 0 && lstat(file, &st) == 0)
                    {
                        if(st.st_size == 0)
                        {
                            return conn->push_chunk(conn, HTTP_NO_CONTENT, strlen(HTTP_NO_CONTENT));
                        }
                        else if((n = http_req.headers[HEAD_REQ_IF_MODIFIED_SINCE]) > 0
                                && str2time(http_req.hlines + n) == st.st_mtime)
                        {
                            return conn->push_chunk(conn, HTTP_NOT_MODIFIED, strlen(HTTP_NOT_MODIFIED));
                        }
                        else
                        {
                            p = buf;
                            p += sprintf(p, "HTTP/1.0 200 OK\r\nContent-Length:%lld\r\n"
                                    "Content-Type: text/html;charset=%s\r\n",
                                    (long long int)(st.st_size), http_default_charset);
                            if((n = http_req.headers[HEAD_GEN_CONNECTION]) > 0)
                                p += sprintf(p, "Connection: %s\r\n", http_req.hlines + n);
                            p += sprintf(p, "Last-Modified:");
                            p += GMTstrdate(st.st_mtime, p);
                            p += sprintf(p, "%s", "\r\n");//date end
                            p += sprintf(p, "%s", "\r\n");
                            conn->push_chunk(conn, buf, (p - buf));
                            return conn->push_file(conn, file, 0, st.st_size);
                        }
                    }
                    else
                    {
                        return conn->push_chunk(conn, HTTP_NOT_FOUND, strlen(HTTP_NOT_FOUND));
                    }
                }
                else
                {
                    return conn->push_chunk(conn, HTTP_BAD_REQUEST, strlen(HTTP_BAD_REQUEST));
                }
            }
        }
        else if(http_req.reqid == HTTP_POST)
        {
            if((n = http_req.headers[HEAD_ENT_CONTENT_LENGTH]) > 0
                    && (n = atol(http_req.hlines + n)) > 0)
            {
                conn->save_cache(conn, &http_req, sizeof(HTTP_REQ));
                return conn->recv_chunk(conn, n);
            }
        }
err_end:
        ret = conn->push_chunk(conn, HTTP_BAD_REQUEST, strlen(HTTP_BAD_REQUEST));
    }
    return ret;
}

/*  data handler */
int httpd_data_handler(CONN *conn, CB_DATA *packet, CB_DATA *cache, CB_DATA *chunk)
{
    char *p = NULL, *end = NULL, line[HTTP_LINE_SIZE]; 
    HTTP_REQ httpRQ = {0}, *http_req = NULL;
    int ret = -1, n = 0;

    if(conn && packet && cache && chunk && chunk->ndata > 0)
    {
        if((http_req = (HTTP_REQ *)cache->data))
        {
            if(http_req->reqid == HTTP_POST)
            {
                p = chunk->data;
                end = chunk->data + chunk->ndata;
                //fprintf(stdout, "%s::%d request:%s\n", __FILE__, __LINE__, p);
                if(http_argv_parse(p, end, &httpRQ) == -1)goto err_end;
                //fprintf(stdout, "%s::%d OK\n", __FILE__, __LINE__);
                //if(httpd_request_handler(&httpRQ, &query) <= 0) goto err_end;
                //fprintf(stdout, "%s::%d OK\n", __FILE__, __LINE__);
            }
        }
err_end: 
        ret = conn->push_chunk(conn, HTTP_BAD_REQUEST, strlen(HTTP_BAD_REQUEST));
    }
    return ret;
}

/* httpd timeout handler */
int httpd_timeout_handler(CONN *conn, CB_DATA *packet, CB_DATA *cache, CB_DATA *chunk)
{
    if(conn)
    {
        conn->over_cstate(conn);
        return conn->over(conn);
    }
    return -1;
}

/* OOB data handler for httpd */
int httpd_oob_handler(CONN *conn, CB_DATA *oob)
{
    if(conn)
    {
        return 0;
    }
    return -1;
}

/* chunkd packet handler */
int chunkd_packet_handler(CONN *conn, CB_DATA *packet)
{
    XHEAD *xhead = NULL;
    int ret = -1;

    if(conn && packet && (xhead = (XHEAD *)packet->data))
    {
        if(xhead->length > 0) conn->save_cache(conn, xhead->length);
        else
        {
            switch(xhead->cmd)
            {
                case DBASE_REQ_APPEND:
                case DBASE_REQ_UPDATE:
                case DBASE_REQ_UPDATE:
                    break;
                case DBASE_REQ_DELETE:
                    break;
                case DBASE_REQ_COPY:
                    break;
                case DBASE_REQ_STATE:
                    break;
            }
        }
    }
    return ret;
}

/* data handler */
int chunkd_data_handler(CONN *conn, CB_DATA *packet, CB_DATA *cache, CB_DATA *chunk)
{
    int ret = -1;

    if(conn && packet)
    {

    }
    return ret;
}

/* error handler */
int chunkd_error_handler(CONN *conn, CB_DATA *packet, CB_DATA *cache, CB_DATA *chunk)
{
    int ret = -1;

    if(conn && packet)
    {

    }
    return ret;
}


/* httpd timeout handler */
int chunkd_timeout_handler(CONN *conn, CB_DATA *packet, CB_DATA *cache, CB_DATA *chunk)
{
    if(conn)
    {
        conn->over_cstate(conn);
        return conn->over(conn);
    }
    return -1;
}

/* OOB data handler for httpd */
int chunkd_oob_handler(CONN *conn, CB_DATA *oob)
{
    if(conn)
    {
        return 0;
    }
    return -1;
}
/* add service */
int chunkd_add_service(int serviceid, int port)
{
    SERVICE *chunkd = NULL;
    /* chunkd */
    if((chunkd = service_init()) == NULL)
    {
        fprintf(stderr, "Initialize service failed, %s", strerror(errno));
        _exit(-1);
    }
    chunkd->id = serviceid;
    chunkd->family = iniparser_getint(dict, "CHUNKD:inet_family", AF_INET);
    chunkd->sock_type = iniparser_getint(dict, "CHUNKD:socket_type", SOCK_STREAM);
    chunkd->ip = iniparser_getstr(dict, "CHUNKD:service_ip");
    chunkd->port = iniparser_getint(dict, "CHUNKD:service_port", 1040);
    chunkd->working_mode = iniparser_getint(dict, "CHUNKD:working_mode", WORKING_PROC);
    chunkd->service_type = iniparser_getint(dict, "CHUNKD:service_type", S_SERVICE);
    chunkd->service_name = iniparser_getstr(dict, "CHUNKD:service_name");
    chunkd->nprocthreads = iniparser_getint(dict, "CHUNKD:nprocthreads", 8);
    chunkd->ndaemons = iniparser_getint(dict, "CHUNKD:ndaemons", 0);
    chunkd->use_iodaemon = iniparser_getint(dict, "CHUNKD:use_iodaemon", 1);
    chunkd->use_cond_wait = iniparser_getint(dict, "CHUNKD:use_cond_wait", 0);
    chunkd->set_log(chunkd, iniparser_getstr(dict, "CHUNKD:logfile"));
    chunkd->session.packet_type = PACKET_CERTAIN_LENGTH;
    chunkd->session.packet_length = sizeof(XHEAD);
    chunkd->session.buffer_size = iniparser_getint(dict, "CHUNKD:buffer_size", SB_BUF_SIZE);
    chunkd->session.packet_handler = &chunkd_packet_handler;
    chunkd->session.data_handler = &chunkd_data_handler;
    chunkd->session.timeout_handler = &chunkd_timeout_handler;
    dbs[serviceid] = chunkd;
    ndbs++;
    return sbase->add_service(sbase, chunkd);
}

/* Initialize from ini file */
int sbase_initialize(SBASE *sbase, char *conf)
{
    int i = 0, n = 0, used_for = -1, mmsource_status = 0, 
        index_from = 0, index_count = 0, port = 0;
    char *s = NULL, *p = NULL, *dir = NULL, *disk = NULL, 
         *charset = NULL, line[HTTP_LINE_SIZE];
    void *dp = NULL;

    if((dict = iniparser_new(conf)) == NULL)
    {
        fprintf(stderr, "Initializing conf:%s failed, %s\n", conf, strerror(errno));
        _exit(-1);
    }
    /* http headers map */
    TRIETAB_INIT(http_headers_map);
    if(http_headers_map)
    {
        for(i = 0; i < HTTP_HEADER_NUM; i++)
        {
            p = http_headers[i].e;
            s = line;
            while(*p != '\0')
            {
                if(*p >= 'A' && *p <= 'Z')
                {
                    *s++ = *p++ - ('A' - 'a');
                }
                else *s++ = *p++;
            }
            *s = '\0';
            dp = (void *)((long)(i + 1));
            p = line;
            TRIETAB_ADD(http_headers_map, p, http_headers[i].elen, dp);
        }
    }
    /* SBASE */
    sbase->nchilds = iniparser_getint(dict, "SBASE:nchilds", 0);
    sbase->connections_limit = iniparser_getint(dict, "SBASE:connections_limit", SB_CONN_MAX);
    setrlimiter("RLIMIT_NOFILE", RLIMIT_NOFILE, sbase->connections_limit);
    sbase->usec_sleep = iniparser_getint(dict, "SBASE:usec_sleep", SB_USEC_SLEEP);
    sbase->set_log(sbase, iniparser_getstr(dict, "SBASE:logfile"));
    sbase->set_evlog(sbase, iniparser_getstr(dict, "SBASE:evlogfile"));
    /* httpd */
    is_inside_html = iniparser_getint(dict, "HTTPD:is_inside_html", 1);
    httpd_home = iniparser_getstr(dict, "HTTPD:httpd_home");
    http_default_charset = iniparser_getstr(dict, "HTTPD:httpd_charset");
    /* decode html base64 */
    if(html_code_base64 && (n = strlen(html_code_base64)) > 0
            && (httpd_index_html_code = (unsigned char *)calloc(1, n + 1)))
    {
        nhttpd_index_html_code = base64_decode(httpd_index_html_code,
                (char *)html_code_base64, n);
    }
    if((httpd = service_init()) == NULL)
    {
        fprintf(stderr, "Initialize service failed, %s", strerror(errno));
        _exit(-1);
    }
    httpd->family = iniparser_getint(dict, "HTTPD:inet_family", AF_INET);
    httpd->sock_type = iniparser_getint(dict, "HTTPD:socket_type", SOCK_STREAM);
    httpd->ip = iniparser_getstr(dict, "HTTPD:service_ip");
    httpd->port = iniparser_getint(dict, "HTTPD:service_port", 1080);
    httpd->working_mode = iniparser_getint(dict, "HTTPD:working_mode", WORKING_PROC);
    httpd->service_type = iniparser_getint(dict, "HTTPD:service_type", S_SERVICE);
    httpd->service_name = iniparser_getstr(dict, "HTTPD:service_name");
    httpd->nprocthreads = iniparser_getint(dict, "HTTPD:nprocthreads", 1);
    httpd->ndaemons = iniparser_getint(dict, "HTTPD:ndaemons", 0);
    httpd->use_iodaemon = iniparser_getint(dict, "HTTPD:use_iodaemon", 0);
    httpd->use_cond_wait = iniparser_getint(dict, "HTTPD:use_cond_wait", 0);
    httpd->set_log(httpd, iniparser_getstr(dict, "HTTPD:logfile"));
    httpd->session.packet_type = iniparser_getint(dict, "HTTPD:packet_type",PACKET_DELIMITER);
    httpd->session.packet_delimiter = iniparser_getstr(dict, "HTTPD:packet_delimiter");
    p = s = httpd->session.packet_delimiter;
    while(*p != 0 )
    {
        if(*p == '\\' && *(p+1) == 'n')
        {
            *s++ = '\n';
            p += 2;
        }
        else if (*p == '\\' && *(p+1) == 'r')
        {
            *s++ = '\r';
            p += 2;
        }
        else
            *s++ = *p++;
    }
    *s++ = 0;
    httpd->session.packet_delimiter_length = strlen(httpd->session.packet_delimiter);
    httpd->session.buffer_size = iniparser_getint(dict, "HTTPD:buffer_size", SB_BUF_SIZE);
    httpd->session.packet_reader = &httpd_packet_reader;
    httpd->session.packet_handler = &httpd_packet_handler;
    httpd->session.data_handler = &httpd_data_handler;
    httpd->session.timeout_handler = &httpd_timeout_handler;
    httpd->session.oob_handler = &httpd_oob_handler;
    /* traced */
    if((traced = service_init()) == NULL)
    {
        fprintf(stderr, "Initialize service failed, %s", strerror(errno));
        _exit(-1);
    }
    traced->family = iniparser_getint(dict, "TRACED:inet_family", AF_INET);
    traced->sock_type = iniparser_getint(dict, "TRACED:socket_type", SOCK_STREAM);
    traced->working_mode = iniparser_getint(dict, "TRACED:working_mode", WORKING_PROC);
    traced->service_type = iniparser_getint(dict, "TRACED:service_type", C_SERVICE);
    traced->service_name = iniparser_getstr(dict, "TRACED:service_name");
    traced->nprocthreads = iniparser_getint(dict, "TRACED:nprocthreads", 1);
    traced->ndaemons = iniparser_getint(dict, "TRACED:ndaemons", 0);
    traced->use_iodaemon = iniparser_getint(dict, "TRACED:use_iodaemon", 1);
    traced->use_cond_wait = iniparser_getint(dict, "TRACED:use_cond_wait", 0);
    traced->set_log(traced, iniparser_getstr(dict, "TRACED:logfile"));
    traced->session.packet_type = PACKET_CERTAIN_LENGTH;
    traced->session.packet_length = sizeof(XHEAD);
    traced->session.buffer_size = iniparser_getint(dict, "TRACED:buffer_size", SB_BUF_SIZE);
    traced->session.packet_handler = &traced_packet_handler;
    traced->session.data_handler = &traced_data_handler;
    traced->session.timeout_handler = &traced_timeout_handler;
    /* chunkd */
    memset(dbs, 0, sizeof(SERVICE *) * XDB_DISK_MAX);
    if((p = iniparser_getstr(dict, "CHUNKD:basedir")) && (xdb = xdbase_init(p)))
    {
        /* add disk list */
        if((p = iniparser_getstr(dict, "CHUNKD:disklist")))
        {
            while(*p != '\0')
            {
                while(*p != '\0' && *p < '0' && *p > '9')++p;
                port = atoi(p);
                while(*p != '\0' && *p != '/')++p;
                if(*p != '/') break;
                disk = p;
                while(*p != '\0' && *p != ' ')++p;
                *p++ = '\0';
                xdbase_add_disk(xdb, disk, 0, port);
            }
        }
        /* db service */
        if(xdb->state && xdb->state->nxdisks > 0) 
        {
            for(i = 0; i < XDB_DISK_MAX; i++)
            {
                if(xdb->state->xdisks[i].status)
                    chunkd_add_service(i, xdb->state->xdisks[i].port);
            }
        }
    }
    else
    {
        fprintf(stderr, "Invalid service[CHUNKD] config options\n");
        _exit(-1);
    }
    return (sbase->add_service(sbase, httpd) | sbase->add_service(sbase, traced));
}

static void services_stop(int sig)
{
    switch (sig)
    {
        case SIGINT:
        case SIGTERM:
            fprintf(stderr, "server is interrupted by user.\n");
            if(sbase)sbase->stop(sbase);
            break;
        default:
            break;
    }
    return ;
}

int main(int argc, char **argv)
{
    char *conf = NULL, ch = 0;
    int is_daemon = 0;
    pid_t pid;

    /* get configure file */
    while((ch = getopt(argc, argv, "c:d")) != -1)
    {
        if(ch == 'c') conf = optarg;
        else if(ch == 'd') is_daemon = 1;
    }
    if(conf == NULL)
    {
        fprintf(stderr, "Usage:%s -d -c config_file\n", argv[0]);
        _exit(-1);
    }
    /* locale */
    setlocale(LC_ALL, "C");
    /* signal */
    signal(SIGTERM, &services_stop);
    signal(SIGINT,  &services_stop);
    signal(SIGHUP,  &services_stop);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);
    if(is_daemon)
    {
        pid = fork();
        switch (pid) {
            case -1:
                perror("fork()");
                exit(EXIT_FAILURE);
                break;
            case 0: /* child process */
                if(setsid() == -1)
                    exit(EXIT_FAILURE);
                break;
            default:/* parent */
                _exit(EXIT_SUCCESS);
                break;
        }
    }
    /*setrlimiter("RLIMIT_NOFILE", RLIMIT_NOFILE, 65536)*/
    if((sbase = sbase_init()) == NULL)
    {
        exit(EXIT_FAILURE);
        return -1;
    }
    fprintf(stdout, "Initializing from configure file:%s\n", conf);
    /* Initialize sbase */
    if(sbase_initialize(sbase, conf) != 0 )
    {
        fprintf(stderr, "Initialize from configure file failed\n");
        return -1;
    }
    fprintf(stdout, "Initialized successed\n");
    sbase->running(sbase, 0);
    //sbase->running(sbase, 3600);
    //sbase->running(sbase, 60000000);
    //sbase->stop(sbase);
    sbase->clean(&sbase);
    if(http_headers_map) TRIETAB_CLEAN(http_headers_map);
    if(dict)iniparser_free(dict);
    if(logger){LOGGER_CLEAN(logger);}
    if(httpd_index_html_code) free(httpd_index_html_code);
    return 0;
}
